/**
 * Software License, Version 1.0
 *
 * Copyright 2003 The Trustees of Indiana University.  All rights reserved.
 *
 *
 *Redistribution and use in source and binary forms, with or without
 *modification, are permitted provided that the following conditions are met:
 *
 *1) All redistributions of source code must retain the above copyright notice,
 * the list of authors in the original source code, this list of conditions and
 * the disclaimer listed in this license;
 *2) All redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the disclaimer listed in this license in
 * the documentation and/or other materials provided with the distribution;
 *3) Any documentation included with all redistributions must include the
 * following acknowledgement:
 *
 *"This product includes software developed by the Community Grids Lab. For
 * further information contact the Community Grids Lab at
 * http://communitygrids.iu.edu/."
 *
 * Alternatively, this acknowledgement may appear in the software itself, and
 * wherever such third-party acknowledgments normally appear.
 *
 *4) The name Indiana University or Community Grids Lab or NaradaBrokering,
 * shall not be used to endorse or promote products derived from this software
 * without prior written permission from Indiana University.  For written
 * permission, please contact the Advanced Research and Technology Institute
 * ("ARTI") at 351 West 10th Street, Indianapolis, Indiana 46202.
 *5) Products derived from this software may not be called NaradaBrokering,
 * nor may Indiana University or Community Grids Lab or NaradaBrokering appear
 * in their name, without prior written permission of ARTI.
 *
 *
 * Indiana University provides no reassurances that the source code provided
 * does not infringe the patent or any other intellectual property rights of
 * any other entity.  Indiana University disclaims any liability to any
 * recipient for claims brought by any other entity based on infringement of
 * intellectual property rights or otherwise.
 *
 *LICENSEE UNDERSTANDS THAT SOFTWARE IS PROVIDED "AS IS" FOR WHICH NO
 *WARRANTIES AS TO CAPABILITIES OR ACCURACY ARE MADE. INDIANA UNIVERSITY GIVES
 *NO WARRANTIES AND MAKES NO REPRESENTATION THAT SOFTWARE IS FREE OF
 *INFRINGEMENT OF THIRD PARTY PATENT, COPYRIGHT, OR OTHER PROPRIETARY RIGHTS.
 *INDIANA UNIVERSITY MAKES NO WARRANTIES THAT SOFTWARE IS FREE FROM "BUGS",
 *"VIRUSES", "TROJAN HORSES", "TRAP DOORS", "WORMS", OR OTHER HARMFUL CODE.
 *LICENSEE ASSUMES THE ENTIRE RISK AS TO THE PERFORMANCE OF SOFTWARE AND/OR
 *ASSOCIATED MATERIALS, AND TO THE PERFORMANCE AND VALIDITY OF INFORMATION
 *GENERATED USING SOFTWARE.
 */

package cgl.narada.service.storage.file;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Properties;

import cgl.narada.event.TemplateInfo;
import cgl.narada.event.impl.TemplateInfoImpl;
import cgl.narada.matching.Profile;
import cgl.narada.matching.ProfileReconstructor;
import cgl.narada.matching.string.StringProfile;
import cgl.narada.service.ServiceException;
import cgl.narada.service.reliable.CompanionDbInfo;
import cgl.narada.service.storage.InventoryEvent;
import cgl.narada.service.storage.SequenceDestinations;
import cgl.narada.service.storage.StorageService;
import cgl.narada.util.logging.Logger;
import cgl.narada.util.logging.LoggerFactory;

import com.coyotegulch.jisp.BTreeIndex;
import com.coyotegulch.jisp.BTreeIterator;
import com.coyotegulch.jisp.IndexedObjectDatabase;
import com.coyotegulch.jisp.KeyObject;

/**
 * Storage Service File implement
 *
 * @author Hongbin Liu
 * @version 1.0
 */
public class StorageServiceFileImpl implements StorageService {

  static final Logger                  log       = LoggerFactory
                                                     .getLogger(StorageServiceFileImpl.class);

  static IndexedObjectDatabase         profile_db, template_db,
      entity_template_db, entity_db, entity_inventory_db, inventory_db;
  static BTreeIndex                    profile_index, template_index,
      entity_template_index, entity_index, entity_inventory_index,
      inventory_index1, inventory_index2;
  public static StorageServiceFileImpl fileStore = null;

  public static StorageService getStorageService(Properties props) {
    if (fileStore == null)
      fileStore = new StorageServiceFileImpl(props);
    return fileStore;
  }

  private StorageServiceFileImpl(Properties props) {
    BuildDB builddb = new BuildDB();
    builddb.setProperties(props);
    if (!builddb.build()) {
      log.error("file storage db is not built successfully.");
    }

    profile_db = BuildDB.profDB;
    template_db = BuildDB.tempDB;
    entity_template_db = BuildDB.entTempDB;
    entity_db = BuildDB.entDB;
    entity_inventory_db = BuildDB.entInvDB;
    inventory_db = BuildDB.invDB;

    profile_index = BuildDB.profIndex;
    template_index = BuildDB.tempIndex;
    entity_template_index = BuildDB.entTempIndex;
    entity_index = BuildDB.entIndex;
    entity_inventory_index = BuildDB.entInvIndex;
    inventory_index1 = BuildDB.invIndex;
    inventory_index2 = BuildDB.invIndex2;

    profile_db.attachIndex(profile_index);
    template_db.attachIndex(template_index);
    entity_db.attachIndex(entity_index);
    entity_template_db.attachIndex(entity_template_index);
    entity_inventory_db.attachIndex(entity_inventory_index);
    inventory_db.attachIndex(inventory_index1);
    inventory_db.attachIndex(inventory_index2);
  }

  public void storeProfile(int templateId, Profile profile)
      throws ServiceException {
    try {
      String id = profile.getProfileId();
      int type = profile.getProfileType();
      int dest = profile.getDestination();
      byte[] sub = profile.getBytes();

      KeyObject[] keyArray = { new ProfileKey(id) };
      ProfileRecord pr = new ProfileRecord(id, sub, type, templateId, dest);
      profile_db.write(keyArray, pr);

    } catch (Exception t) {
      t.printStackTrace();
      throw new ServiceException(t.getMessage());
    }
  }

  public void removeProfile(int templateId, Profile profile)
      throws ServiceException {
    try {
      String id = profile.getProfileId();
      KeyObject[] keyArray = { new ProfileKey(id) };
      profile_db.remove(keyArray);

    } catch (Exception t) {
      t.printStackTrace();
      throw new ServiceException(t.getMessage());
    }
  }

  public void storeRegisteredEntity(int entityId) throws ServiceException {
    try {

      KeyObject[] keyArray = { new EntityKey(entityId) };
      EntityRecord er = new EntityRecord(entityId);
      entity_db.write(keyArray, er);

    } catch (Exception t) {
      t.printStackTrace();
      throw new ServiceException(t.getMessage());
    }
  }

  /** Removes a registered entity. This method should also remove the entity
   from any profiles that it was subscribed to and and templateId to which
   it was registered */
  public void removeRegisteredEntity(int entityId) throws ServiceException {
    try {
      KeyObject[] keyArray = { new EntityKey(entityId) };
      entity_db.remove(keyArray);
    } catch (Exception t) {
      t.printStackTrace();
      throw new ServiceException(t.getMessage());
    }
  }

  public void storeTemplate(TemplateInfo templateInfo) throws ServiceException {
    try {
      int tID = templateInfo.getTemplateId();
      int tType = templateInfo.getTemplateType();
      byte[] bytes = templateInfo.getBytes();

      KeyObject[] keyArray = { new TemplateKey(tID) };
      TemplateRecord tr = new TemplateRecord(tID, tType, bytes);
      template_db.write(keyArray, tr);

    } catch (Exception t) {
      t.printStackTrace();
      throw new ServiceException(t.getMessage());
    }
  }

  public void removeTemplateManagement(int templateId) throws ServiceException {
    try {
      KeyObject[] keyArray = { new TemplateKey(templateId) };
      template_db.remove(keyArray);

    } catch (Exception t) {
      t.printStackTrace();
      throw new ServiceException(t.getMessage());
    }
  }

  public void storeEntityForTemplate(int eID, int tID) throws ServiceException {
    try {
      EntityTemplateRecord etr = new EntityTemplateRecord(tID, eID, 0, 0);
      KeyObject[] keyArray = { new EntityTemplateKey(tID, eID) };
      entity_template_db.write(keyArray, etr);

    } catch (Exception t) {
      t.printStackTrace();
      throw new ServiceException(t.getMessage());
    }
  }

  public void removeEntityFromTemplate(int eID, int tID)
      throws ServiceException {
    try {
      KeyObject[] keyArray = { new EntityTemplateKey(tID, eID) };
      entity_template_db.remove(keyArray);

    } catch (Exception t) {
      t.printStackTrace();
      throw new ServiceException(t.getMessage());
    }
  }

  public int[] getListOfRegisteredEntities() throws ServiceException {
    /* can use BtreeIterator but need to know beginning index */
    ArrayList al = new ArrayList();
    try {
      BTreeIterator iterator = new BTreeIterator(entity_index);
      boolean m_active = true;
      while (m_active) {
        EntityRecord rec = (EntityRecord) entity_db.read(iterator);
        if (rec != null) {
          al.add(new Integer(rec.entityID));
        }
        m_active = iterator.moveNext();
      }
    } catch (Exception t) {
      t.printStackTrace();
      throw new ServiceException(t.getMessage());
    }
    int len = al.size();
    int[] entities = new int[len];
    for (int i = 0; i < len; i++)
      entities[i] = ((Integer) al.get(i)).intValue();
    return entities;
  }

  public void printEntityInventory() {
    try {
      BTreeIterator iterator = new BTreeIterator(entity_inventory_index);
      boolean m_active = true;
      System.out.println(">>>EntityInventory<<<");
      System.out.println("seqNum    entityID");
      while (m_active) {
        //System.out.println(">>>printEntityInventory active<<<");
        EntityInventoryRecord rec = (EntityInventoryRecord) entity_inventory_db
            .read(iterator);
        if (rec != null) {
          System.out.println("" + rec.seqNum + "    " + rec.entityID);
        }
        m_active = iterator.moveNext();
      }
    } catch (Exception t) {
      t.printStackTrace();
    }
  }

  public void printInventory() {
    try {
      BTreeIterator iterator = new BTreeIterator(inventory_index2);
      boolean m_active = true;
      System.out.println(">>>Inventory<<<");
      System.out.println("seqNum tempID");
      while (m_active) {
        //InventoryRecord rec = (InventoryRecord)database.read(iterator);
        InventoryKey2 rec = (InventoryKey2) iterator.getKey();
        if (rec != null) {
          //System.out.println(""+rec.seqNum+"    "+rec.previousSeqNum+"    "+rec.templateID);
          System.out.println("" + rec.seqNum + "    " + rec.templateID);
        }
        m_active = iterator.moveNext();
      }
    } catch (Exception t) {
      t.printStackTrace();
    }
  }

  public void printEntityTemplate() {
    try {
      BTreeIterator iterator = new BTreeIterator(entity_template_index);
      boolean m_active = true;
      System.out.println(">>>EntityTemplate<<<");
      System.out.println("tempID  entityID  catenation  syncpoint");
      while (m_active) {
        EntityTemplateRecord rec = (EntityTemplateRecord) entity_template_db
            .read(iterator);
        //EntityTemplateKey rec = (EntityTemplateKey) iterator.getKey();
        if (rec != null) {
          System.out.println("" + rec.templateID + "    " + rec.entityID
              + "    " + rec.catenation + "    " + rec.syncPoint);
          //System.out.println(""+rec.templateID+"    "+rec.entityID);
        }
        m_active = iterator.moveNext();
      }
    } catch (Exception t) {
      t.printStackTrace();
    }
  }

  public int[] getListOfRegisteredEntities(int tID) throws ServiceException {

    ArrayList al = new ArrayList();
    try {
      BTreeIterator iterator = new BTreeIterator(entity_template_index);
      boolean m_active = true;
      while (m_active) {
        EntityTemplateRecord rec = (EntityTemplateRecord) entity_template_db
            .read(iterator);
        if (rec != null && rec.templateID == tID) {
          al.add(new Integer(rec.entityID));
        }
        m_active = iterator.moveNext();
      }
    } catch (Exception t) {
      t.printStackTrace();
      throw new ServiceException(t.getMessage());
    }
    int len = al.size();
    int[] entities = new int[len];
    for (int i = 0; i < len; i++)
      entities[i] = ((Integer) al.get(i)).intValue();
    return entities;
  }

  //we focus on StringProfile
  public Profile[] getListOfRegisteredProfiles(int templateId)
      throws ServiceException {

    ArrayList al = new ArrayList();
    try {
      BTreeIterator iterator = new BTreeIterator(profile_index);
      boolean m_active = true;
      while (m_active) {
        ProfileRecord rec = (ProfileRecord) profile_db.read(iterator);
        if (rec != null && rec.templateID == templateId) {
          byte[] profileBytes = rec.subscriptions;
          int type = rec.profileType;
          Profile profile = ProfileReconstructor.getProfile(type, profileBytes);
          al.add(profile);
        }
        m_active = iterator.moveNext();
      }
    } catch (Exception t) {
      t.printStackTrace();
      throw new ServiceException(t.getMessage());
    }
    return (Profile[]) al.toArray(new StringProfile[0]);
  }

  public TemplateInfo[] getListOfManagedTemplates() throws ServiceException {

    ArrayList al = new ArrayList();
    try {
      BTreeIterator iterator = new BTreeIterator(template_index);
      boolean m_active = true;
      while (m_active) {
        TemplateRecord rec = (TemplateRecord) template_db.read(iterator);
        if (rec != null) {
          byte[] bytes = rec.templateBytes;
          TemplateInfo template = new TemplateInfoImpl(bytes);
          al.add(template);
        }
        m_active = iterator.moveNext();
      }
    } catch (Exception t) {
      t.printStackTrace();
      throw new ServiceException(t.getMessage());
    }
    return (TemplateInfo[]) al.toArray(new TemplateInfoImpl[0]);
  }

  public void store(CompanionDbInfo companionDbInfo, InventoryEvent iEvent, SequenceDestinations seqDestinations)
      throws ServiceException {
    /*
     * can change to storing event bytes in a separate file;
     * only keep a pointer to the file name in the record
     */
    try {
      long seq = iEvent.getSequenceNumber();
      long preSeq = iEvent.getPreviousSequenceNumber();
      int tID = iEvent.getTemplateId();
      byte[] eBytes = iEvent.getBytes();
      int[] destArray = seqDestinations.getDestinations();

      //KeyObject[] keyArray = {new InventoryKey(seq)};
      KeyObject[] keyArray = { new InventoryKey(seq),
          new InventoryKey2(seq, tID) };
      InventoryRecord ir = new InventoryRecord(seq, preSeq, tID, eBytes);
      inventory_db.write(keyArray, ir);

      /*
       * another feasible way to construct EntityInventory database is to
       * have (seq, int[]) rather than (seq, int) row record. depending on
       * if there  will be query about seq given certain entityID, this
       * may or maynot be a good idea
       */
      for (int i = 0; i < destArray.length; i++) {
        KeyObject[] keys = { new EntityInventoryKey(seq, destArray[i]) };
        EntityInventoryRecord eir = new EntityInventoryRecord(seq, destArray[i]);
        entity_inventory_db.write(keys, eir);
      }
    } catch (Exception t) {

      t.printStackTrace();
      throw new ServiceException(t.getMessage());
    }
  }

  public InventoryEvent getStoredEvent(long seq) throws ServiceException {
    /*
     * if Inventory database stores pointer to event file instead of
     * actual event bytes then logic below should change. to be preemptive
     * with very large event, everything now being event bytes should be
     * file name, so avoid unnecessary copy in memory.
     */
    InventoryEvent ie = null;
    try {
      InventoryKey key = new InventoryKey(seq);
      InventoryRecord ir = (InventoryRecord) inventory_db.read(key,
          inventory_index1);
      ie = new InventoryEvent(ir.event);

    } catch (Exception t) {
      t.printStackTrace();
      throw new ServiceException(t.getMessage());
    }
    return ie;
  }

  public void processAcknowledgement(long seq, int eID) throws ServiceException {

    EntityInventoryRecord eir = null;
    try {
      EntityInventoryKey key = new EntityInventoryKey(seq, 0);
      eir = (EntityInventoryRecord) entity_inventory_db.read(key,
          entity_inventory_index);
      KeyObject[] keys = { new EntityInventoryKey(seq, eID) };
      if (eir == null) {
        /* need to check back with JISP to see if it remove old
         * when writing the new one according to its API
         */
        entity_inventory_db.remove(keys);
        KeyObject[] r_keys = { new EntityInventoryKey(seq, 0) };
        entity_inventory_db.write(r_keys, new EntityInventoryRecord(seq, 0));
      } else {
        entity_inventory_db.remove(keys);
      }

    } catch (Exception t) {
      t.printStackTrace();
      throw new ServiceException(t.getMessage());
    }
  }

  public long[] retrieveMissedSequences(long startingAt, long endingAt,
      int templateId, int entityId) throws ServiceException {
    ArrayList al = new ArrayList();
    try {
      long it = startingAt;
      while (it <= endingAt) {
        EntityInventoryKey eiKey = new EntityInventoryKey(it, entityId);
        EntityInventoryRecord eir = (EntityInventoryRecord) entity_inventory_db
            .read(eiKey, entity_inventory_index);
        if (eir != null) {
          //get Inventory record whose seq and templateID is (it, templateId)
          InventoryKey2 iKey2 = new InventoryKey2(it, templateId);
          InventoryRecord ir = (InventoryRecord) inventory_db.read(iKey2,
              inventory_index2);
          if (ir != null)
            al.add(new Long(it));
        }
        it++;
      }
    } catch (Exception t) {
      t.printStackTrace();
      throw new ServiceException(t.getMessage());
    }
    int len = al.size();
    if (len == 0)
      return null;
    long[] seqArray = new long[len];
    for (int i = 0; i < len; i++)
      seqArray[i] = ((Long) al.get(i)).longValue();
    return seqArray;
  }

  public void advanceSyncpoint(long syncpoint, int templateId, int entityId)
      throws ServiceException {

    try {
      EntityTemplateKey etk = new EntityTemplateKey(templateId, entityId);
      KeyObject[] keys = { etk };
      EntityTemplateRecord etr = (EntityTemplateRecord) entity_template_db
          .read(etk, entity_template_index);
      entity_template_db.remove(keys);
      entity_template_db.write(keys, new EntityTemplateRecord(templateId,
          entityId, etr.catenation, syncpoint));

    } catch (Exception t) {
      t.printStackTrace();
      throw new ServiceException(t.getMessage());
    }
  }

  public long getSyncpoint(int templateId, int entityId)
      throws ServiceException {
    EntityTemplateRecord etr = getSyncOrCatHelp(templateId, entityId);
    return etr.syncPoint;
  }

  public void advanceCatenation(int catenation, int templateId, int entityId)
      throws ServiceException {
    try {
      EntityTemplateKey etk = new EntityTemplateKey(templateId, entityId);
      KeyObject[] keys = { etk };
      EntityTemplateRecord etr = (EntityTemplateRecord) entity_template_db
          .read(etk, entity_template_index);
      entity_template_db.remove(keys);
      entity_template_db.write(keys, new EntityTemplateRecord(templateId,
          entityId, catenation, etr.syncPoint));

    } catch (Exception t) {
      t.printStackTrace();
      throw new ServiceException(t.getMessage());
    }
  }

  public int getCatenation(int templateId, int entityId)
      throws ServiceException {

    EntityTemplateRecord etr = getSyncOrCatHelp(templateId, entityId);
    return etr.catenation;
  }

  public long getSequenceNumberLastAssigned() throws ServiceException {
    InventoryKey iMax = null;
    EntityInventoryKey eiMax = null;

    try {
      BTreeIterator iterator = new BTreeIterator(inventory_index1);

      iMax = (InventoryKey) iterator.getKey();
      boolean m_active = iterator.moveNext();
      while (m_active) {
        InventoryKey cur = (InventoryKey) iterator.getKey();
        if (cur.seqKey > iMax.seqKey)
          iMax = cur;
        m_active = iterator.moveNext();
      }

      BTreeIterator iterator2 = new BTreeIterator(entity_inventory_index);

      eiMax = (EntityInventoryKey) iterator2.getKey();
      m_active = iterator2.moveNext();
      while (m_active) {
        EntityInventoryKey cur2 = (EntityInventoryKey) iterator2.getKey();
        if (cur2.seqNum > eiMax.seqNum)
          eiMax = cur2;
        m_active = iterator2.moveNext();
      }

      if (iMax.seqKey == eiMax.seqNum) {
        return iMax.seqKey;
      } else if (iMax.seqKey > eiMax.seqNum) {
        KeyObject[] keys = { new InventoryKey(iMax.seqKey) };
        inventory_db.remove(keys);
      } else if (iMax.seqKey < eiMax.seqNum) {
        KeyObject[] keys = { new EntityInventoryKey(eiMax.seqNum,
            eiMax.entityID) };
        entity_inventory_db.remove(keys);
      }
    } catch (Exception t) {
      t.printStackTrace();
      throw new ServiceException(t.getMessage());
    }
    return (iMax.seqKey > eiMax.seqNum ? eiMax.seqNum : iMax.seqKey);
  }

  //make (seqNum,  templateID) a second index to speed up this method
  public long getSequenceNumberLastAssigned(int templateId)
      throws ServiceException {

    InventoryKey2 iMax = null;
    try {
      BTreeIterator iterator = new BTreeIterator(inventory_index2);
      iMax = (InventoryKey2) iterator.getKey();
      boolean m_active = iterator.moveNext();
      while (m_active) {
        InventoryKey2 cur = (InventoryKey2) iterator.getKey();
        if (cur.seqNum > iMax.seqNum)
          iMax = cur;
        m_active = iterator.moveNext();
      }
    } catch (Exception t) {
      t.printStackTrace();
      throw new ServiceException(t.getMessage());
    }
    return iMax.seqNum;
  }

  private EntityTemplateRecord getSyncOrCatHelp(int templateId, int entityId)
      throws ServiceException {

    EntityTemplateRecord etr = null;
    try {
      EntityTemplateKey etk = new EntityTemplateKey(templateId, entityId);
      etr = (EntityTemplateRecord) entity_template_db.read(etk,
          entity_template_index);

    } catch (Exception t) {
      t.printStackTrace();
      throw new ServiceException(t.getMessage());
    }

    if (etr == null)
      throw new ServiceException("no such EntityTemplate record: "
          + "\ntemplateID " + templateId + "\nentityId " + entityId);
    else
      return etr;
  }

  public long[] checkIfSequencesBelongToTemplate(int templateId,
      long[] sequences) throws ServiceException {

    return null;
  }

  public long[] retrieveSequences(long startingAt, long endingAt,
      int templateId, int maxOccur) throws ServiceException {
    return null;
  }

  public LinkedList retrieveSequencesAndTimestamps(int templateId)
      throws ServiceException {
    return null;
  }

  public LinkedList retrieveSequencesAndTimestamps(long startingTimestamp,
			long endingTimestamp, int templateId) throws ServiceException {
  	return null;
  }


  public long retrieveTimestamp(int templateId, long sequenceNumber)
      throws ServiceException {
    return 0;
  }

  public LinkedList retrieveSequencesAndTimestampsStartingAt(
      long startingSequenceNumber, long endingTimestamp, int templateId)
      throws ServiceException {
    return null;
  }

  public LinkedList retrieveSequencesByTimestamps(long startingTimestamp,
      long endingTimestamp, int templateId) throws ServiceException {
    return null;
  }

  public long getTimestamp(long sequnceNumber, int templateId)
      throws ServiceException {
    return 0;
  }

  public LinkedList retrieveMinimumSequeceNumberAndTimestamp(int temlateId) throws ServiceException {
  	return null;
  }

  public LinkedList retrieveMaximumSequeceNumberAndTimestamp(int temlateId) throws ServiceException {
  	return null;
  }
}
